package tester

import (
	"fmt"
	"sync/atomic"

	"gitee.com/AlexWoo/arpc/alog"
	"gitee.com/AlexWoo/arpc/anet"
)

const (
	queueSize = 4096
)

var testid uint32

type Net interface {
	Send(*anet.Msg, *tester)
}

type TestRecorder struct {
	success uint32
	failed  uint32
}

func (tr *TestRecorder) Success() {
	atomic.AddUint32(&tr.success, 1)
}

func (tr *TestRecorder) Failed() {
	atomic.AddUint32(&tr.failed, 1)
}

type tester struct {
	net Net
	tr  *TestRecorder
	id  uint32

	lctx *alog.LogCtx

	recvQ chan *anet.Msg
	vars  map[string]string

	recvmsg *anet.Msg

	sc *script
}

func newTester(net Net, tr *TestRecorder, sc *script) *tester {
	t := &tester{
		net:   net,
		tr:    tr,
		id:    atomic.AddUint32(&testid, 1),
		recvQ: make(chan *anet.Msg, queueSize),
		vars:  map[string]string{},
		sc:    sc,
	}

	t.lctx = &alog.LogCtx{
		Prefix: func() string { return fmt.Sprintf("[tester: %d]", t.id) },
	}

	go t.run()

	return t
}

func (t *tester) recvMsg(msg *anet.Msg) {
	t.recvQ <- msg
}

func (t *tester) sendMsg(msg *anet.Msg, te *tester) {
	t.net.Send(msg, te)
}

func (t *tester) getVal(va string) string {
	if va[0] == '$' { // var
		va = va[1:]

		return t.vars[va]
	}

	if va[0] == '@' { // func
		return getFuncVal(va[1:])
	}

	return va
}

func (t *tester) run() {
	st := t.sc.steps[t.sc.entry]

	for {
		alog.LogInfo(t.lctx, "script run step: %s", st.name)
		ns := st.run(t)

		switch ns {
		case stepErr:
			t.tr.Failed()
			alog.LogInfo(t.lctx, "script error end at %s", st.name)
			return
		case stepEnd:
			t.tr.Success()
			alog.LogInfo(t.lctx, "script normal end at %s", st.name)
			return
		default:
			st = t.sc.steps[ns]
		}
	}
}
